﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ChoWorkflowServer.Model;
using Cinchoo.Core.Net.Sockets;
using Cinchoo.Core.Diagnostics;
using System.Threading.Tasks;
using System.Threading;
using Cinchoo.Core;
using Cinchoo.Core.Net;

namespace ChoWorkflowServer
{
    public class ChoWSControllerConnectionManager : IDisposable
    {
        #region Instance Data Members (Private)

        private readonly object _padLock = new object();
        private readonly Dictionary<ChoConnectionState, ChoSubscribeCommand> _clients = new Dictionary<ChoConnectionState, ChoSubscribeCommand>();
        private readonly Dictionary<ChoConnectionState, ChoSubscribeCommand> _servers = new Dictionary<ChoConnectionState, ChoSubscribeCommand>();
        private readonly object _machinesHashSetLock = new object();
        private readonly Dictionary<string, string> _machinesHashSet = new Dictionary<string, string>();
        private readonly Task _pingConnectionsTask = null;
        private ChoTcpServer _tcpServer = null;
        //private readonly ChoConnectionManagerSettings _connectionManagerSettings = new ChoConnectionManagerSettings();
        //public event EventHandler<ChoEventArgs<Tuple<ChoConnectionState, object>>> OnObjectRecieved = delegate { };;

        #endregion Instance Data Members (Private)

        public ChoWSControllerConnectionManager(ChoTcpServer tcpServer)
        {
            ChoGuard.ArgumentNotNull(tcpServer, "TcpServer");

            //Add <ANY> machine to machineset collection
            //_machinesHashSet.Add(ChoClusterManager.ANY_CLUSTER_NAME);

            ChoClusterManager.Default.NodeAdd += ((o, e) =>
            {
                lock (_machinesHashSetLock)
                {
                    if (!_machinesHashSet.ContainsKey(e.Value))
                        _machinesHashSet.Add(e.Value, GetIPAddress(e.Value));
                }
            });

            ChoClusterManager.Default.NodeRemove += ((o, e) =>
            {
                lock (_machinesHashSetLock)
                {
                    if (_machinesHashSet.ContainsKey(e.Value))
                        _machinesHashSet.Remove(e.Value);
                }
            });

            ChoClusterManager.Default.ClusterReset += ((o, e) =>
            {
                lock (_machinesHashSetLock)
                {
                    _machinesHashSet.Clear();
                }
            });

            //Load All cluster names
            //lock (_machinesHashSetLock)
            //{
            //    foreach (string cluster in ChoClusterManager.Default.AllClusters)
            //        _machinesHashSet.Add(cluster);
            //}

            _tcpServer = tcpServer;
            //_tcpServer.OnObjectRecieved = ((st, e) =>
            //{
            //    OnObjectRecieved.Raise(null, new ChoEventArgs<Tuple<ChoConnectionState, object>>(new Tuple<ChoConnectionState, object>(st, e)));
            //});

            _pingConnectionsTask = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    ChoConnectionState[] clients = null;
                    ChoConnectionState[] servers = null;

                    lock (_padLock)
                    {
                        clients = _clients.Keys.ToArray();
                        servers = _servers.Keys.ToArray();

                        foreach (ChoConnectionState state in clients)
                        {
                            try
                            {
                                if (!state.Send(ChoCommandMessage.DefaultPingMsg))
                                    Unsubscribe(state);
                            }
                            catch (Exception ex)
                            {
                                ChoTrace.Write(ex);
                                Unsubscribe(state);
                            }
                        }
                        foreach (ChoConnectionState state in servers)
                        {
                            try
                            {
                                if (!state.Send(ChoCommandMessage.DefaultPingMsg))
                                    Unsubscribe(state);
                            }
                            catch (Exception ex)
                            {
                                ChoTrace.Write(ex);
                                Unsubscribe(state);
                            }
                        }
                    }
                    Thread.Sleep(5000);
                }
            });
            _pingConnectionsTask.LogExceptions();
        }

        [ChoStreamProfile("Subscribers/Unsubscibers...", Name = "wfSubscribers", OuterProfileName = null)]
        public void Subscribe(ChoSubscribeCommand cmd, ChoConnectionState connectionState)
        {
            if (cmd == null) return;

            lock (_padLock)
            {
                ChoProfile.WriteLine("Subscribe: {0}, Type: {1}", cmd.HostName, cmd.SubscriberType);
                if (cmd.SubscriberType == ChoSubscriberType.Server)
                {
                    _servers.Add(connectionState, cmd);
                    AddToMachineHashSet(cmd.HostName, cmd.ClusterName, cmd.IsPrivateNode);
                    //_connectionManagerSettings.InvokeOnServerConnectPlugIns(cmd.HostName);

                    ChoCommandMessage commandMessage = NewMachinesCommand();
                    SendCommandToAllClients(commandMessage);

                    //ChoWSUDPConnectionManager.Instance.ServerHostListMsg = new ChoWSServerHostListMsg() { WSServerHostNames = ((ChoMachinesCommand)commandMessage.Command).Machines };
                }
                else if (cmd.SubscriberType == ChoSubscriberType.Client)
                {
                    _clients.Add(connectionState, cmd);

                    ChoCommandMessage commandMessage = NewMachinesCommand();
                    SendTo(connectionState, commandMessage);

                    commandMessage = NewWFListCommand();
                    SendTo(connectionState, commandMessage);

                    commandMessage = NewClientCtrlCommand();
                    SendTo(connectionState, commandMessage);

                    //commandMessage = NewUsersACLChangedCommand();
                    //SendTo(connectionState, commandMessage);
                }
            }
        }

        [ChoStreamProfile("Subscribers/Unsubscibers...", Name = "wfSubscribers", OuterProfileName = null)]
        public void Unsubscribe(ChoConnectionState connectionState)
        {
            lock (_padLock)
            {
                if (_clients.ContainsKey(connectionState))
                {
                    ChoSubscribeCommand cmd = _clients[connectionState];
                    ChoProfile.WriteLine("Unsubscribe: {0}, Type: {1}", cmd.HostName, ChoSubscriberType.Client);
                    _clients.Remove(connectionState);
                }
                else if (_servers.ContainsKey(connectionState))
                {
                    ChoSubscribeCommand cmd = _servers[connectionState];
                    ChoProfile.WriteLine("Unsubscribe: {0}, Type: {1}", cmd.HostName, ChoSubscriberType.Server);
                    _servers.Remove(connectionState);

                    RemoveMachineFromHashSet(cmd.HostName);

                    ChoCommandMessage commandMessage = NewMachinesCommand();
                    SendCommandToAllClients(commandMessage);
                }
            }
        }

        public void BroadcastCommand(ChoCommandMessage payload)
        {
            if (_tcpServer != null)
            {
                ChoCommandMessage lCommandMessage = payload.Clone();
                lCommandMessage.Universalize();
                _tcpServer.Broadcast(lCommandMessage);
            }
        }

        public void SendCommandToServer(string machineName, ChoCommandMessage payload)
        {
            ChoConnectionState[] states = GetServerConnectionState(machineName);
            if (states == null) return;

            foreach (ChoConnectionState state in states)
            {
                SendTo(state, payload);
            }
        }

        public void SendTo(ChoConnectionState state, ChoCommandMessage payload)
        {
            lock (_padLock)
            {
                try
                {
                    if (!state.Send(payload))
                        Unsubscribe(state);
                }
                catch (Exception ex)
                {
                    ChoTrace.Write(ex);
                    Unsubscribe(state);
                }
            }
        }

        public void SendCommandToAllServers(ChoCommandMessage payload)
        {
            KeyValuePair<ChoConnectionState, ChoSubscribeCommand>[] keyValuePairs = null;

            lock (_padLock)
            {
                keyValuePairs = _servers.ToArray();

                if (keyValuePairs != null)
                {
                    foreach (KeyValuePair<ChoConnectionState, ChoSubscribeCommand> keyValuePair in keyValuePairs)
                    {
                        try
                        {
                            if (!keyValuePair.Key.Send(payload))
                                Unsubscribe(keyValuePair.Key);
                        }
                        catch (Exception ex)
                        {
                            ChoTrace.Write(ex);
                            Unsubscribe(keyValuePair.Key);
                        }
                    }
                }
            }
        }

        //public void SendCommandToClient(string machineName, ChoCommandMessage payload)
        //{
        //    ChoConnectionState[] states = GetClientConnectionState(machineName);
        //    if (states == null) return;

        //    foreach (ChoConnectionState state in states)
        //    {
        //        try
        //        {
        //            if (!state.Send(payload))
        //                Unsubscribe(state);
        //        }
        //        catch (Exception ex)
        //        {
        //            ChoTrace.Write(ex);
        //            Unsubscribe(state);
        //        }
        //    }
        //}

        public void SendCommandToAllClients(ChoCommandMessage payload)
        {
            KeyValuePair<ChoConnectionState, ChoSubscribeCommand>[] keyValuePairs = null;

            lock (_padLock)
            {
                keyValuePairs = _clients.ToArray();

                if (keyValuePairs != null)
                {
                    foreach (KeyValuePair<ChoConnectionState, ChoSubscribeCommand> state in keyValuePairs)
                    {
                        try
                        {
                            if (!state.Key.Send(payload))
                                Unsubscribe(state.Key);
                        }
                        catch (Exception ex)
                        {
                            ChoTrace.Write(ex);
                            Unsubscribe(state.Key);
                        }
                    }
                }
            }
        }

        internal void AddToMachineHashSet(string serverName, string clusterName = null, bool isPrivateNode = false)
        {
            if (serverName.IsNullOrWhiteSpace()) return;

            if (!clusterName.IsNullOrWhiteSpace())
                ChoClusterManager.Default.AddNodeToCluster(clusterName, serverName, isPrivateNode);
            else
                ChoClusterManager.Default.AddNode(serverName);
        }

        internal void RemoveMachineFromHashSet(string serverName)
        {
            if (serverName.IsNullOrWhiteSpace()) return;

            foreach (string clusterName in ChoClusterManager.Default.FindClustersForNode(serverName))
            {
                if (!clusterName.IsNullOrWhiteSpace())
                    ChoClusterManager.Default.RemoveNodeFromCluster(clusterName, serverName);
            }
            //ChoClusterManager.Default.RemoveNode(serverName);
        }

        internal void PublishServerListToClients()
        {
            ChoCommandMessage commandMessage = NewMachinesCommand();

            SendCommandToAllClients(commandMessage);
        }

        private string GetIPAddress(string hostName)
        {
            try
            {
                return ChoIPAddress.GetIPAddress(hostName);
            }
            catch
            {
                return null;
            }
        }

        private ChoCommandMessage NewMachinesCommand()
        {
            //Send new server list to all clients
            ChoCommandMessage commandMessage = ChoCommandMessage.New();
            commandMessage.Universalize();

            ChoMachinesCommand machinesCommand = new ChoMachinesCommand();
            commandMessage.Command = machinesCommand;
            machinesCommand.Machines = ServerMachines;
            return commandMessage;
        }

        private ChoCommandMessage NewWFListCommand()
        {
            //Send new server list to all clients
            ChoCommandMessage commandMessage = ChoCommandMessage.New();
            commandMessage.Universalize();

            ChoWFListCommand wfListCommand = new ChoWFListCommand();
            commandMessage.Command = wfListCommand;
            wfListCommand.WFNames = new List<string>(ChoWorkflowDefManager.WFNames);
            return commandMessage;
        }

        private ChoCommandMessage NewClientCtrlCommand()
        {
            //Send new server list to all clients
            ChoCommandMessage commandMessage = ChoCommandMessage.New();
            commandMessage.Universalize();

            ChoClientCtrlCommand clientCtrlCommand = new ChoClientCtrlCommand();
            commandMessage.Command = clientCtrlCommand;
            clientCtrlCommand.MaxNoOfAllowedTasksReached = ChoWorkflowServerEx.Instance.MaxNoOfAllowedTasksReached;
            return commandMessage;
        }

        private ChoCommandMessage NewUsersACLChangedCommand()
        {
            //Send new server list to all clients
            ChoCommandMessage commandMessage = ChoCommandMessage.New();
            commandMessage.Universalize();

            ChoUsersACLChangedCommand clientCtrlCommand = new ChoUsersACLChangedCommand();
            commandMessage.Command = clientCtrlCommand;
            return commandMessage;
        }

        internal void PublishWorkflowListToClients()
        {
            ChoCommandMessage commandMessage = NewWFListCommand();

            SendCommandToAllClients(commandMessage);
        }

        internal void PublishClientCtrlMsgToClients()
        {
            ChoCommandMessage commandMessage = NewClientCtrlCommand();

            SendCommandToAllClients(commandMessage);
        }

        internal ChoConnectionState[] GetServerConnectionState(string machineName)
        {
            lock (_padLock)
            {
                return (from keyValue in _servers
                        where keyValue.Value.HostName == machineName
                        select keyValue.Key).ToArray();
            }
        }

        private ChoConnectionState[] GetClientConnectionState(string machineName)
        {
            lock (_padLock)
            {
                return (from keyValue in _clients
                        where keyValue.Value.HostName == machineName
                        select keyValue.Key).ToArray();
            }
        }

        public void Dispose()
        {
            _tcpServer = null;
            ChoClusterManager.Default.Reset();
        }

        private KeyValuePair<string, string>[] ServerMachines
        {
            get
            {
                lock (_machinesHashSetLock)
                {
                    return _machinesHashSet.AsEnumerable().ToArray();
                }
            }
        }
    }
}
